package com.ponhu.ea2.stockcenter.provider.service.rpc.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.common.utils.CollectionUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ponhu.ea2.stockcenter.dto.GWallWarehouseDto;
import com.ponhu.ea2.stockcenter.dto.WMSEventInfoDTO;
import com.ponhu.ea2.stockcenter.dto.WMSInventoryInfo;
import com.ponhu.ea2.stockcenter.enums.DefinedSystem;
import com.ponhu.ea2.stockcenter.enums.ExchangeActionTypeEnum;
import com.ponhu.ea2.stockcenter.provider.constants.CommonConstant;
import com.ponhu.ea2.stockcenter.provider.decorators.Idempotence;
import com.ponhu.ea2.stockcenter.provider.entity.StockInventoryReal;
import com.ponhu.ea2.stockcenter.provider.entity.StockWarehouse;
import com.ponhu.ea2.stockcenter.provider.entity.StockWarehouseRelation;
import com.ponhu.ea2.stockcenter.provider.mapper.StockInventoryRealMapper;
import com.ponhu.ea2.stockcenter.provider.service.biz.stockwarehouse.WarehouseStockResponseService;
import com.ponhu.ea2.stockcenter.provider.service.entity.StockInventoryRealService;
import com.ponhu.ea2.stockcenter.provider.service.entity.StockInventoryWarehouseTraceService;
import com.ponhu.ea2.stockcenter.provider.service.entity.StockWarehouseRelationService;
import com.ponhu.ea2.stockcenter.provider.service.entity.StockWarehouseService;
import com.ponhu.ea2.stockcenter.provider.support.exception.StockRuntimeException;
import com.ponhu.ea2.stockcenter.provider.support.service.RedisSupportService;
import com.ponhu.ea2.stockcenter.provider.utils.CommonUtils;
import com.ponhu.ea2.stockcenter.provider.utils.DingTalkHandler;
import com.ponhu.ea2.stockcenter.provider.utils.ProBeanUtils;
import com.ponhu.ea2.stockcenter.provider.utils.RedisUtils;
import com.ponhu.ea2.stockcenter.service.bms.StockCommonService;
import com.ponhu.ea2.stockcenter.service.bms.StockForWMSService;
import com.ponhu.ea2.stockcenter.vo.DingTalkContentVO;
import com.ponhu.ea2.stockcenter.vo.RealityLevelStockInfoVO;
import com.ponhu.ea2.stockcenter.vo.ReturnResponseBean;
import io.netty.util.concurrent.FastThreadLocal;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.annotation.DubboService;
import org.apache.dubbo.config.annotation.Method;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

import static com.ponhu.ea2.stockcenter.enums.ResponseCodeEnum.PARAMS_INCORRECT_EXCEPTION_900003;
import static com.ponhu.ea2.stockcenter.enums.ResponseCodeEnum.STOCK_WAREHOUSE_EXCEPTION_900106;

@Slf4j
@DubboService(version = "1.0.0", interfaceClass = StockForWMSService.class, timeout = 60000,
        methods = {@Method(name = "receiveWMSEventHandler", timeout = 600000)})
public class StockForWMSServiceImpl implements StockForWMSService {

    @Autowired
    private DingTalkHandler dingTalkHandler;
    @Autowired
    private FastThreadLocal<String> traceIdLocal;
    @Autowired
    private RedisSupportService redisSupportService;
    @Autowired
    private StockCommonService stockCommonService;
    @Autowired
    private StockInventoryRealService stockInventoryRealService;
    @Autowired
    private WarehouseStockResponseService warehouseStockResponseService;
    @Autowired
    private StockWarehouseRelationService stockWarehouseRelationService;
    @Autowired
    private StockInventoryWarehouseTraceService stockInventoryWarehouseTraceService;
    @Resource
    private StockWarehouseService stockWarehouseService;
    @Resource
    private StockInventoryRealMapper stockInventoryRealMapper;

    /**
     * 根号业务单号(链路号)关闭任务链路
     *
     * @param docNo 业务单号(链路号)
     * @return
     */
    @Override
    public ReturnResponseBean<Boolean> updTraceDone(String docNo) {
        if (CommonUtils.isEmpty(docNo)) {
            return ReturnResponseBean.error(PARAMS_INCORRECT_EXCEPTION_900003);
        }

        // 关闭任务链和记录任务链操作需要并发互斥
        String redisKey = RedisUtils.getRedisKey(CommonConstant.REDISSON_RW_LOCK_TRACE_UPD, docNo);
        Boolean updTraceDone = redisSupportService.wrapWithWriteLock(redisKey, () ->
                stockInventoryWarehouseTraceService.updTraceDone(docNo));

        return ReturnResponseBean.ok(updTraceDone);
    }

    /**
     * 根据货品编码查询货品仓库库存信息
     *
     * @param inventoryCodeList 货品编码列表
     * @return
     */
    @Override
    public ReturnResponseBean<List<RealityLevelStockInfoVO>> getRealityStockInfo(List<String> inventoryCodeList) {

        // 获取分布式读写锁的key值
        List<String> keys = inventoryCodeList.stream().map(code -> RedisUtils
                .getRedisKey(CommonConstant.REDISSON_RW_LOCK_STOCK_GLOBAL, code)).collect(Collectors.toList());

        // 将查询放到读锁中
        List<StockWarehouseRelation> stockWarehouseRelations = redisSupportService.wrapWithReadLock(keys,
                () -> stockWarehouseRelationService.getRelationsByInventoryCodes(inventoryCodeList));

        // 处理查询结果
        List<RealityLevelStockInfoVO> realityLevelStockInfoVOList = stockWarehouseRelations
                .parallelStream().map(swr -> new RealityLevelStockInfoVO()
                        .setInventoryCode(swr.getInventoryCode())
                        .setWarehouseCode(swr.getWarehouseCode())
                        .setRealStockNum(swr.getRealStockNum())
                        .setAvailableStockNum(swr.getAvailableStockNum())
                        .setLockedStockNum(swr.getLockedStockNum()))
                .collect(Collectors.toList());

        return new ReturnResponseBean<>(realityLevelStockInfoVOList);
    }

    /**
     * 对接WMS系统事件处理接口
     *
     * @param stockRequestToken 请求库存中心token
     * @param eventInfoDTO      需要处理的事件信息DTO
     * @return
     */
    @Override
    @Idempotence
    public ReturnResponseBean receiveWMSEventHandler(String stockRequestToken, WMSEventInfoDTO eventInfoDTO) {

        ReturnResponseBean paramCheckResponseBean = receiveEventParamCheck(eventInfoDTO);
        if (!paramCheckResponseBean.isOk()) return paramCheckResponseBean;

        List<ExchangeActionTypeEnum> batchEnumList = Arrays.asList(
                ExchangeActionTypeEnum.TRANSFER_PLAN_IN,
                ExchangeActionTypeEnum.SALES_RETURN_IN,
                ExchangeActionTypeEnum.CHECK_PROFIT_IN,
                ExchangeActionTypeEnum.PURCHASE_IN,
                ExchangeActionTypeEnum.OTHER_IN,
                ExchangeActionTypeEnum.TRANSFER_PLAN_OUT,
                ExchangeActionTypeEnum.CHECK_LOSS_OUT,
                ExchangeActionTypeEnum.OTHER_OUT);
        if (batchEnumList.contains(eventInfoDTO.getActionType())) {
            StockForWMSService stockForWMSService = (StockForWMSService) AopContext.currentProxy();
            String servletPath = "dubbo facade interface StockForWMSServiceImpl.receiveWMSEventHandler";
            String traceId = traceIdLocal.get();

            // 将请求拆分进行分批执行
            AtomicInteger traceNo = new AtomicInteger(1);
            eventInfoDTO.getInventoryInfoList().parallelStream().forEach(info -> {
                WMSEventInfoDTO wmsEventInfoDTO = ProBeanUtils
                        .copyProperties(eventInfoDTO, WMSEventInfoDTO.class)
                        .setInventoryInfoList(Arrays.asList(info));
                try {
                    traceIdLocal.set(String.format("%s-%s", traceId, traceNo.getAndIncrement()));
                    warehouseStockResponseService.receiveWMSEventHandler(wmsEventInfoDTO);

                    // 入库事件处理成功后相关货品Redis缓存的出库事件再次处理
                    List<ExchangeActionTypeEnum> inEnumList = Arrays.asList(
                            ExchangeActionTypeEnum.TRANSFER_PLAN_IN,
                            ExchangeActionTypeEnum.SALES_RETURN_IN,
                            ExchangeActionTypeEnum.CHECK_PROFIT_IN,
                            ExchangeActionTypeEnum.PURCHASE_IN,
                            ExchangeActionTypeEnum.OTHER_IN);
                    if (inEnumList.contains(wmsEventInfoDTO.getActionType())) {
                        CompletableFuture.runAsync(() -> {
                            String redisKey = RedisUtils.getRedisKey(CommonConstant.WMS_OUT_EVENT_HANDLER_ERROR,
                                    wmsEventInfoDTO.getInWarehouseCode(), info.getInventoryCode());
                            // 获取Redis缓存的出库事件请求入参并删除
                            String outStr = redisSupportService.bucketGetAndDel(redisKey, String.class);
                            if (StringUtils.isNotEmpty(outStr)) {
                                log.info("{} ===>> [{}] ===>> 触发[Redis缓存处理] ===>> 触发请求入参={} ===>> Redis缓存请求入参={}",
                                        servletPath, traceId, JSON.toJSONString(wmsEventInfoDTO), outStr);

                                ReturnResponseBean<String> token = stockCommonService.getStockRequestToken(DefinedSystem.WMS);
                                stockForWMSService.receiveWMSEventHandler(token.getData(), JSON.parseObject(outStr, WMSEventInfoDTO.class));
                            }
                        });
                    }
                } catch (Exception e) {
                    if (e instanceof StockRuntimeException) {
                        StockRuntimeException ex = (StockRuntimeException) e;
                        log.error("{} ===>> [{}] ===>> 入参={} ===>> 分批出/入库%业务异常：" +
                                        "[code={}, msg={}]", servletPath, traceId, JSON.toJSONString(wmsEventInfoDTO),
                                eventInfoDTO.isFirstFrequency() ? "" : "[Redis缓存处理]", ex.getCode(), ex.getMessage());

                        // 事件处理异常则将其关联货品编码加入Redis缓存待定时任务修复处理
                        CompletableFuture.runAsync(() -> {
                            if (ex.getCodeEnum() == STOCK_WAREHOUSE_EXCEPTION_900106) return;

                            String inventoryCode = wmsEventInfoDTO.getInventoryInfoList().get(0).getInventoryCode();
                            redisSupportService.rSetAdd(CommonConstant.WMS_EVENT_HANDLER_ERROR, inventoryCode);
                        });

                        // 出库事件业务异常后对其事件进行缓存待后续入库事件将其激活后再次执行
                        CompletableFuture.runAsync(() -> {
                            if (ex.getCodeEnum() == STOCK_WAREHOUSE_EXCEPTION_900106) return;

                            List<ExchangeActionTypeEnum> outEnumList = Arrays.asList(
                                    ExchangeActionTypeEnum.TRANSFER_PLAN_OUT,
                                    ExchangeActionTypeEnum.CHECK_LOSS_OUT,
                                    ExchangeActionTypeEnum.OTHER_OUT);
                            if (outEnumList.contains(wmsEventInfoDTO.getActionType()) && wmsEventInfoDTO.getFrequency() < 11) {
                                String redisKey = RedisUtils.getRedisKey(CommonConstant.WMS_OUT_EVENT_HANDLER_ERROR,
                                        wmsEventInfoDTO.getOutWarehouseCode(), info.getInventoryCode());
                                redisSupportService.bucketSet(redisKey, JSON.toJSONString(wmsEventInfoDTO.addFrequency()), 24, TimeUnit.HOURS);

                                log.error("{} ===>> [{}] ===>> [出库事件处理异常，对出库事件进行Redis缓存] ===>> 处理次数={} ===>> 入参={}",
                                        servletPath, traceId, wmsEventInfoDTO.getFrequency(), JSON.toJSONString(wmsEventInfoDTO));
                            } else { // inEnumList 或 (outEnumList && Redis缓存处理)

                                // 业务异常则发生钉钉报警
                                CompletableFuture.runAsync(() -> {
                                    DingTalkContentVO dingTalkContentVO = new DingTalkContentVO();
                                    dingTalkContentVO.setPath(servletPath);
                                    dingTalkContentVO.setTraceId(traceId);
                                    dingTalkContentVO.setRequestParams(JSON.toJSONString(wmsEventInfoDTO));
                                    dingTalkContentVO.setExceptionType("分批出/入库业务异常");
                                    dingTalkContentVO.setExceptionCode(String.valueOf(Objects.toString(ex.getCode(), "")));
                                    dingTalkContentVO.setExceptionMsg(ex.getMessage());

                                    dingTalkHandler.sendMsg(dingTalkContentVO);
                                });
                            }
                        });
                    } else { // 程序异常
                        e.printStackTrace();
                        String throwableMsg = "";
                        if (e.getStackTrace() != null && e.getStackTrace().length > 0) {
                            throwableMsg = e.getStackTrace()[0].toString();
                        }
                        log.error("{} ===>> [{}] ===>> 入参={} ===>> 分批出/入库%s程序异常：" +
                                        "[msg={}, causeby={}]", servletPath, traceId, JSON.toJSONString(wmsEventInfoDTO),
                                eventInfoDTO.isFirstFrequency() ? "" : "[Redis缓存处理]", e.getMessage(), throwableMsg);
                    }
                }
            });

            return ReturnResponseBean.ok();
        }

        return warehouseStockResponseService.receiveWMSEventHandler(eventInfoDTO);
    }

    public ReturnResponseBean receiveEventParamCheck(WMSEventInfoDTO eventInfoDTO) {
        // WMS 动作类型
        ExchangeActionTypeEnum actionType = eventInfoDTO.getActionType();
        if (ObjectUtils.isEmpty(actionType)) {
            throw new StockRuntimeException(PARAMS_INCORRECT_EXCEPTION_900003, "动作类型不可为空");
        }

        // 调拨申请相关
        List<ExchangeActionTypeEnum> transferEnumList = Arrays.asList(
                ExchangeActionTypeEnum.TRANSFER_APPLY_CANCEL,
                ExchangeActionTypeEnum.TRANSFER_OUT,
                ExchangeActionTypeEnum.TRANSFER_IN);

        // 出库相关
        List<ExchangeActionTypeEnum> outEnumList = Arrays.asList(
                ExchangeActionTypeEnum.SALES_RETURN_CANCLE_OUT,
                ExchangeActionTypeEnum.PURCHASE_CANCEL_OUT,
                ExchangeActionTypeEnum.PURCHASE_RETURN_OUT,
                ExchangeActionTypeEnum.TRANSFER_PLAN_OUT,
                ExchangeActionTypeEnum.CHECK_LOSS_OUT,
                ExchangeActionTypeEnum.OTHER_OUT,
                ExchangeActionTypeEnum.SALES_OUT);

        // 入库相关
        List<ExchangeActionTypeEnum> inEnumList = Arrays.asList(
                ExchangeActionTypeEnum.TRANSFER_PLAN_IN,
                ExchangeActionTypeEnum.SALES_RETURN_IN,
                ExchangeActionTypeEnum.CHECK_PROFIT_IN,
                ExchangeActionTypeEnum.PURCHASE_IN,
                ExchangeActionTypeEnum.OTHER_IN);

        // WMS 相关全部调拨动作
        List<ExchangeActionTypeEnum> actionTypeEnumList = new ArrayList<>();
        actionTypeEnumList.add(ExchangeActionTypeEnum.TRANSFER_APPLY);
        actionTypeEnumList.addAll(transferEnumList);
        actionTypeEnumList.addAll(outEnumList);
        actionTypeEnumList.addAll(inEnumList);
        if (!actionTypeEnumList.contains(actionType)) {
            throw new StockRuntimeException(PARAMS_INCORRECT_EXCEPTION_900003, "动作类型错误");
        }

        String transferNo = eventInfoDTO.getTransferNo();
        String inWarehouseCode = eventInfoDTO.getInWarehouseCode();
        Boolean inWarehouseIsSale = eventInfoDTO.getInWarehouseIsSale();
        String outWarehouseCode = eventInfoDTO.getOutWarehouseCode();
        Boolean outWarehouseIsSale = eventInfoDTO.getOutWarehouseIsSale();
        List<WMSInventoryInfo> inventoryInfoList = eventInfoDTO.getInventoryInfoList();

        if (actionType == ExchangeActionTypeEnum.TRANSFER_APPLY) {
            if (StringUtils.isEmpty(transferNo)) {
                throw new StockRuntimeException(PARAMS_INCORRECT_EXCEPTION_900003, "调拨单号不可为空");
            }
            if (StringUtils.isEmpty(inWarehouseCode) || ObjectUtils.isEmpty(inWarehouseIsSale)) {
                throw new StockRuntimeException(PARAMS_INCORRECT_EXCEPTION_900003, "调入仓库编码和调入仓库是否可售均不可为空");
            }
            if (StringUtils.isEmpty(outWarehouseCode) || ObjectUtils.isEmpty(outWarehouseIsSale)) {
                throw new StockRuntimeException(PARAMS_INCORRECT_EXCEPTION_900003, "调出仓库编码调出仓库是否可售均不可为空");
            }
            return checkListParam(eventInfoDTO);
        }

        if (transferEnumList.contains(actionType)) {
            if (StringUtils.isEmpty(transferNo)) {
                throw new StockRuntimeException(PARAMS_INCORRECT_EXCEPTION_900003, "调拨单号不可为空");
            }
        }

        if (outEnumList.contains(actionType)) {
            if (StringUtils.isEmpty(outWarehouseCode) || ObjectUtils.isEmpty(outWarehouseIsSale)) {
                throw new StockRuntimeException(PARAMS_INCORRECT_EXCEPTION_900003, "调出仓库编码和调出仓库是否可售均不可为空");
            }
            // 销售出库和采退出库必须有关联业务单号
            List<ExchangeActionTypeEnum> occupyOutEnumList = Arrays.asList(
                    ExchangeActionTypeEnum.SALES_OUT);
            if (occupyOutEnumList.contains(actionType) && CommonUtils.isEmpty(eventInfoDTO.getDocNo())) {
                throw new StockRuntimeException(PARAMS_INCORRECT_EXCEPTION_900003, "销售出库必须有关联业务单号");
            }
            return checkListParam(eventInfoDTO);
        }

        if (inEnumList.contains(actionType)) {
            if (StringUtils.isEmpty(inWarehouseCode) || ObjectUtils.isEmpty(inWarehouseIsSale)) {
                throw new StockRuntimeException(PARAMS_INCORRECT_EXCEPTION_900003, "调入仓库编码和调入仓库是否可售均不可为空");
            }
            return checkListParam(eventInfoDTO);
        }

        return ReturnResponseBean.ok();
    }

    public ReturnResponseBean checkListParam(WMSEventInfoDTO eventInfoDTO) {
        List<WMSInventoryInfo> inventoryInfoList = eventInfoDTO.getInventoryInfoList();
        if (CollectionUtils.isEmpty(inventoryInfoList)) {
            throw new StockRuntimeException(PARAMS_INCORRECT_EXCEPTION_900003, "货品信息列表不可为空");
        } else {
            boolean anyMatch = inventoryInfoList.stream().anyMatch(info ->
                    StringUtils.isEmpty(info.getInventoryCode()) || ObjectUtils.isEmpty(info.getInventoryQuantity()));
            if (anyMatch) throw new StockRuntimeException(PARAMS_INCORRECT_EXCEPTION_900003, "货品信息列表不可为空");

            anyMatch = inventoryInfoList.stream().anyMatch(info -> StringUtils.isNotEmpty(info.getBatchCode()));
            if (anyMatch && (ObjectUtils.isEmpty(eventInfoDTO.getOperatorTime()) || StringUtils.isEmpty(eventInfoDTO.getOperatorName())))
                throw new StockRuntimeException(PARAMS_INCORRECT_EXCEPTION_900003, "批次操作时间和批次操作人不可为空");
        }

        return ReturnResponseBean.ok();
    }

    @Override
    public List<String> getAllStockInventoryReal(int size) {
        LambdaQueryWrapper<StockInventoryReal> queryWrapper = Wrappers
                .lambdaQuery(StockInventoryReal.class)
                .eq(StockInventoryReal::getStatus, 1)
                .last("LIMIT " + size);

        return stockInventoryRealService.list(queryWrapper)
                .stream().map(StockInventoryReal::getInventoryCode).collect(Collectors.toList());
    }

    /**
     * 保存或更新巨沃仓库数据
     *
     * @param warehouseDtoList 巨沃仓库数据
     * @param updateBy         操作人
     * @return
     */
    @Override
    public ReturnResponseBean<Boolean> saveOrUpdateWarehouseInfo(List<GWallWarehouseDto> warehouseDtoList, String updateBy) {
        if (com.baomidou.mybatisplus.core.toolkit.CollectionUtils.isEmpty(warehouseDtoList)) {

            log.info("未获取到任何仓库数据，同步结束");
            return ReturnResponseBean.error(PARAMS_INCORRECT_EXCEPTION_900003.getCode(), "仓库数据不能为空");
        }

        List<StockWarehouse> stockWarehouseList = stockWarehouseService.list();

        List<StockWarehouse> batchList = new ArrayList<>();
        boolean isUpdateSalesNum = false;
        for (GWallWarehouseDto phWarehouseDto : warehouseDtoList) {
            StockWarehouse warehouse = stockWarehouseList.stream().filter(
                    f -> f.getWarehouseCode().equals(phWarehouseDto.getWarehouseCode())).findFirst().orElse(null);

            if (warehouse == null) {
                //添加
                StockWarehouse stockWarehouse = new StockWarehouse();
                BeanUtils.copyProperties(phWarehouseDto, stockWarehouse);
                stockWarehouse.setId(null);
                stockWarehouse.setCreateTime(null);
                stockWarehouse.setSaleStatus(phWarehouseDto.getSaleStatus() == 1);

                batchList.add(stockWarehouse);
            } else {
                //更新
                if (warehouse.getSaleStatus() != null && phWarehouseDto.getSaleStatus() != null && (
                        (warehouse.getSaleStatus() && phWarehouseDto.getSaleStatus() == 0) || (!warehouse.getSaleStatus() && phWarehouseDto.getSaleStatus() == 1)
                )) {
                    isUpdateSalesNum = true;
                }

                Integer id = warehouse.getId();
                BeanUtils.copyProperties(phWarehouseDto, warehouse);
                warehouse.setId(id);
                warehouse.setCreateTime(null);
                warehouse.setSaleStatus(phWarehouseDto.getSaleStatus() == 1);

                batchList.add(warehouse);
            }
        }

        //同步仓库数据
        stockWarehouseService.saveOrUpdateBatch(batchList);
        //更新可售数
        if (isUpdateSalesNum) {
            log.info("更新可售数");
            updateStockSalesNum();
        }

        log.info("同步仓库信息成功: 操作人: {}", updateBy);
        return ReturnResponseBean.ok(true);
    }

    private void updateStockSalesNum() {
        stockInventoryRealMapper.updateStockSalesNum();
    }

}
